﻿/*
   Created 2009 March 15 Randall Maas
   Copyright (c) 2009, Randall Maas
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:

   Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

   Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

   Neither the name of Blackwood Designs, LLC nor the names of its contributors
   may be used to endorse or promote products derived from this software without
   specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
   THE POSSIBILITY OF SUCH DAMAGE.
*/


using System;
using System . IO;
using NAudio.Wave;

class Program
{
   static void Main(string[] args)
   {
      #region Parse Arguments
      if (args . Length < 1)
        {
           Console . WriteLine("No arguments given\n");
           Console . WriteLine(" [in filename] [out filename] [cutOff db] [lowGain db] [highGain db]\n");
           return ;
        }

      string InFileName = args[0];
      string OutFileName;
      if (args . Length < 2)
        OutFileName = String . Format("{0} AGCd{1}", Path . GetFileNameWithoutExtension(InFileName), Path . GetExtension(InFileName));
       else
        OutFileName = args[1];

      if (args . Length < 3)
        GainControl . CutOff_dB = 50;
       else
        try{GainControl . CutOff_dB = int . Parse(args[2]);}
        catch
        {
           Console . WriteLine("cutOff db must be an integer");
           return ;
        }

      if (args . Length < 4)
        GainControl . LowGain_dB = 63;
       else
        try{GainControl . LowGain_dB = int . Parse(args[3]);}
        catch
        {
           Console . WriteLine("lowGain db must be an integer");
           return ;
        }

      if (args . Length < 5)
        GainControl . TgtGain_dB = (GainControl . LowGain_dB + 10) < 74 ? 74 : GainControl . LowGain_dB + 10;
       else
        try{GainControl . TgtGain_dB = int . Parse(args[4]);}
        catch
        {
           Console . WriteLine("highGain db must be an integer");
           return ;
        }
      #endregion

      WaveStream In = null;
      try
      {
         if (InFileName . EndsWith(".mp3"))
           In = new Mp3FileReader(InFileName);
         else if (InFileName.EndsWith(".wav"))
           In = new WaveFileReader(InFileName);
      }
      catch{}
      if (null == In)
        {
           Console . WriteLine("couldn't open the file\n");
           return;
        }
      if (In.WaveFormat.Encoding != WaveFormatEncoding.Pcm)
        In = WaveFormatConversionStream.CreatePcmStream(In);

      int OutSampleRate = In . WaveFormat . SampleRate;
      switch(In . WaveFormat . SampleRate)
      {
         case 16000:
         case 44100:
         case 48000: break;
         default:
           OutSampleRate = 44100;
           return;
      }

      // Write the results out
      In . Seek(0, SeekOrigin . Begin);
      WaveFileWriter.CreateWaveFile(OutFileName, new A1(In, OutSampleRate));
   }
}


/// <summary>
/// A class that hooks the NAudio's file access to the Automatic Gain Control
/// </summary>
public class A1:WaveStream
{
   long       length = long . MaxValue;
   WaveFormat WF;
   WaveStream In;
   int InBuffIdx;

   // The sample window defaults to a sample size of about 10ms
   const double WindowDuration = 0.010;

   bool Done = false;

   int    OutSize;
   object[] ChannelBuffer;
   GainControl[] AGC;

   byte[] ReadBuffer;
   double InInc = 1.0;
   int bufferSize ;
   byte[] OutBuffer;
   public A1(WaveStream In, int OutSampleRate)
   {
      WF = new WaveFormat(OutSampleRate, 16, In . WaveFormat . Channels);
      this . In = In;

      bufferSize = (int)(WindowDuration * OutSampleRate);
      if (bufferSize % 2 == 1)
        bufferSize++;
      int SamplesPerWindow = bufferSize/2;

      ChannelBuffer=new object[In . WaveFormat . Channels];
      AGC = new GainControl[In . WaveFormat . Channels];
      for (int I = 0; I < ChannelBuffer.Length; I++)
      {
         Int16[] PCM16Buffer = new Int16[10+bufferSize/2];
         ChannelBuffer[I] = PCM16Buffer;
         AGC[I] = new GainControl(OutSampleRate, SamplesPerWindow);
         AGC[I] . DoLP = false;
         
      }


      int ReadBufferSize = bufferSize * In . WaveFormat . SampleRate / OutSampleRate ;
      while (ReadBufferSize % In . BlockAlign != 0)
        ReadBufferSize++;
      ReadBuffer = new byte[ReadBufferSize* In.WaveFormat.Channels];

      if (16000 != OutSampleRate && 48000 != OutSampleRate)
        InInc         = In . WaveFormat . SampleRate / 44100.0;
      OutBuffer   = new byte[bufferSize* In . WaveFormat . Channels];
   }

   public override WaveFormat  WaveFormat { get {return WF;} }

   public override bool CanRead { get {return true;} }
   public override bool CanSeek { get {return false;} }
   public override bool CanWrite { get {return true;} }
   public override long Length { get {return length;}}//ut . Length;} }
   int Idx = 0;
   public override long Position { get {return Idx;} set {Idx = (int)value;} }

   int N=0;
   void RefillOutBuffer()
   {
      int NumChannels = In . WaveFormat . Channels, J = 10;
      int L = 10 + bufferSize/2;

      while (!Done && J < L)
      {
         // Refill read buffer
         if (InBuffIdx +1 >= N)
           {
              InBuffIdx = 0;
              N = In . Read(ReadBuffer, 0, ReadBuffer . Length);
              if (N < ReadBuffer . Length)
                Done = true;
           }

         // Convert samples to 16bit PCM 
         if (1.0 <= InInc)
           for (double K = InBuffIdx; J < L; K += InInc)
           {
              InBuffIdx = 2 * NumChannels * (int) K;
              if (InBuffIdx + 1 >= N) break;
              for (int Chan = 0; Chan < NumChannels; Chan++)
              {
                 Int16[] PCM16Buffer = (Int16[]) ChannelBuffer[Chan];
                 PCM16Buffer[J] = (Int16) ((UInt16)ReadBuffer[InBuffIdx+2*Chan] | (((UInt16)ReadBuffer[2*Chan+InBuffIdx +1]) << 8));
              }

              J++;
           }
          else
           for (double K = InBuffIdx; InBuffIdx + 1 < N && J < L; K += InInc)
           {
              InBuffIdx = 2* NumChannels * (int) K;
              int KI = InBuffIdx+2*NumChannels;
              if (InBuffIdx + 1 >= N) break;
              for (int Chan = 0; Chan < NumChannels; Chan++)
              {
                 Int16[] PCM16Buffer = (Int16[]) ChannelBuffer[Chan];
                 Int16 SampleA = (Int16) ((UInt16)ReadBuffer[2*Chan+InBuffIdx] | (((UInt16)ReadBuffer[2*Chan+InBuffIdx +1]) << 8));
                 if (KI + 1 < N)
                   {
                      Int16 SampleB = (Int16) ((UInt16)ReadBuffer[2*Chan+KI] | (((UInt16)ReadBuffer[2*Chan+KI +1]) << 8));
                      PCM16Buffer[J] = (Int16)( (1.0-(KI-K)) * SampleA + (K-KI) * SampleB);
                   }
                  else
                   PCM16Buffer[J] = SampleA;
              }
              J++;
           }
      }

      
      OutIdx = 0;
      byte[] TmpOutBuffer   = new byte[bufferSize];
      int MaxS=0;
      for (int Chan = 0; Chan < NumChannels; Chan++)
      {
         Int16[] PCM16Buffer = (Int16[]) ChannelBuffer[Chan];
         for (int K=J; K < PCM16Buffer.Length; K++)
          PCM16Buffer[K] = 0;

         OutSize = AGC[Chan] . Process(PCM16Buffer, 10, TmpOutBuffer);
         if (MaxS < OutSize) MaxS = OutSize;

         // Move the sliding window of the previous 10 samples into the start
         // of the PCM16Buffer
         int M = PCM16Buffer.Length-10;
         for (int K = 0; K < 10; K++,M++)
          PCM16Buffer[K] = PCM16Buffer[M];

         // Multiplex the TmpOutBuffer into the
         for (int I = 0, K = 2*Chan;  I < bufferSize; K+=2*NumChannels)
         {
            OutBuffer[K]   = TmpOutBuffer[I++];
            OutBuffer[K+1] = TmpOutBuffer[I++];
         }
      }
      OutSize = bufferSize*NumChannels;

      if (NumChannels == 1)
        {
           OutBuffer = TmpOutBuffer;
           return ;
        }

   }

   int OutIdx;


   public override int Read(byte[] buffer, int offset, int count)
   {
      int NRead=0;
      while (count > 0)
      {
         int Amt = count ;
         if (Amt > OutSize) Amt = OutSize;
         Array . Copy(OutBuffer, OutIdx,  buffer,offset, Amt);
         offset += Amt;
         count  -= Amt;
         OutSize-= Amt;
         NRead  += Amt;
         OutIdx += Amt;

         if (OutSize == 0 && !Done)
           RefillOutBuffer();
         else if (Done)
           break;
      }
        
      return NRead;
   }

}

